CloudFormation一撃でWeb上のファイル(Python)を指定してLambda関数を作成してみた
創立記念日のブログの大波にのらなきゃ、このビッグウェーブに。
とおもって、ウエーブ -> ウエブ(ちょと違う) -> WWWeb
Web上のコードをLambdaにしてみるか。と、久しぶりにコーディングしてみました
前から思っていたのですが、URLを指定してLambda関数作れたらいいなと。
CloudFormationのテンプレートに直接書くこともできるんですが、こまかくいろいろ制限がありますし
かといって、S3にzipとして、毎回置くのもちょっと手間なんすよね。
やりたいことは、URLを指定して、それが、Lambda関数でほしいんよ!!!(純正実装はよ)
とわがまま、言ってもしょうがないので、ないものは作るか。と作りはじめたら創立記念日は過ぎていました。
例のごとく、カスタムリソースでの処理なので一見さんお断りなんですが、コードはオープンにしてますので、ご自由に改変、お使いください。
なお、URLで指定するファイルは、Pythonのコードを前提としてます、作成する関数もPythonとしていますので、ご了承ください おそらく、このブログが必要な人はこのままで使うことはないと思いますので、適時調整してください
処理概要
- URLで指定された複数ファイルをLambdaでダウンロードします
- S3にzipで固めてアップロードします
- zipからLambda関数(Python)を作成します
カスタムリソースで、1.2の処理を実装してます。
テンプレート
パラメータPython
Url
に作成するソースとなるファイルを指定します。デフォルトではまさにこのカスタムリソースのPythonファイルのURLを指定しています。
UrlCfnResponse
はpythonのcfn-responseモジュールを指定しています(URLを増やすと、まとめてzipにするようになっています)
LambdaFunctionHandler
はURLで指定するファイル名また呼び出す関数を指定してくださいLambda関数のHandlerです。
テンプレートファイル
https://github.com/ambasad/create-lambda-from-urls/blob/main/create-lambda-from-urls.yml
AWSTemplateFormatVersion: '2010-09-09' Parameters: ZipFileName: Type: String Default: my-deployment-package.zip LambdaFunctionHandler: Type: String Default: copy-to-s3-zip.lambda_handler Url: Type: String Default: https://raw.githubusercontent.com/ambasad/create-lambda-from-urls/master/copy-to-s3-zip.py UrlCfnResponse: Type: String Default: https://raw.githubusercontent.com/awslabs/aws-cloudformation-templates/master/aws/services/CloudFormation/MacrosExamples/StackMetrics/lambda/cfnresponse.py Resources: MyFunction: DependsOn: - S3CopyAndZipped Type: AWS::Lambda::Function Properties: Role: !GetAtt MyFunctionRole.Arn Runtime: python3.8 Handler: !Ref LambdaFunctionHandler Code: S3Bucket: !Ref S3CopyAndZippedBucket S3Key: !Ref ZipFileName MyFunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Path: / ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" S3CopyAndZippedBucket: Type: AWS::S3::Bucket S3CopyAndZipped: Type: Custom::CopyToS3 Properties: ServiceToken: !GetAtt CopyFilesToS3AndZipped.Arn S3BucketName: !Ref S3CopyAndZippedBucket ZipFileName: !Ref ZipFileName Urls: - !Ref Url - !Ref UrlCfnResponse CopyFilesToS3AndZipped: Type: AWS::Lambda::Function Properties: Role: !GetAtt CopyFilesToS3AndZippednRole.Arn Runtime: python3.8 Handler: index.lambda_handler Code: ZipFile: | import os import urllib.request from urllib.parse import urlparse import json import boto3 import cfnresponse import zipfile print('Loading function') s3 = boto3.resource('s3') def save_to_local(url): urlPath = urlparse(url).path fileName = os.path.basename(urlPath) filePath = '/tmp/' + fileName urllib.request.urlretrieve(url, filePath) return filePath, fileName def upload_to_s3(filePath, bucket): fileName = os.path.basename(filePath) s3.Object(bucket, fileName).put(Body=open(filePath, 'rb')) def copy_to_s3(url, bucket, zipFilePath): filePath, fileName = save_to_local(url) upload_to_s3(filePath, bucket) with zipfile.ZipFile(zipFilePath, "a", zipfile.ZIP_DEFLATED) as zf: zf.write(filePath, fileName) def lambda_handler(event, context): print('Received event: ' + json.dumps(event, indent=2)) if event['RequestType'] == 'Create': # get the properties set in the CloudFormation resource properties = event['ResourceProperties'] urls = properties['Urls'] bucket = properties['S3BucketName'] zipFileName = properties['ZipFileName'] try: zipFilePath = '/tmp/' + zipFileName for url in urls: copy_to_s3(url, bucket, zipFilePath) s3.Bucket(bucket).upload_file(zipFilePath, zipFileName) except Exception as e: print(e) cfnresponse.send(event, context, cfnresponse.FAILED, { 'Response': 'Failure'}) return cfnresponse.send(event, context, cfnresponse.SUCCESS, {'Response': 'Success'}) CopyFilesToS3AndZippednRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: root PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:* Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - s3:PutObject Resource: '*' Outputs: MyFunction: Value: !Ref MyFunction S3CopyAndZippedBucket: Value: !Ref S3CopyAndZippedBucket
作成後のリソース
Lambda関数
S3 バケット
スタックを削除する場合は、バケットにURLから取得したファイル、また圧縮したzipが残っているので、オブジェクトを手動で削除してください
まとめ
処理自体は、参考情報に上げているURLのコードがほとんどです。 エラー処理などはいれてないので、ぜひブラッシュアップして使ってください。 作成する関数のRoleの権限は適時調整ください。 Githubにソースはあるんだけど、Lambdaにするのにひと手間かかるんだよねっていう時につかいますかね?
どうだろう・・・ピンポイントで1人くらいに刺さると嬉しいです。
カスタムリソース部分を外部のプロバイダ化すると思ったよりも潰しが効きそうな気はします。
参考情報
https://github.com/lrakai/lambda-copy-to-s3
https://www.dcom-web.co.jp/lab/cloud/aws/compress_files_uploaded_to_s3_using_lambda